import { defineAsyncComponent, mergeProps, createApp, h } from 'vue';

export default async (App, options) => {
  let app, context, ctx, key, props, el, update, destroy;
  options = Object.assign({}, options);
  context = options.context || this;
  ctx = context && context._ ? context._ : context || {};
  key = (Number(String(Math.random()).slice(2)) + Date.now()).toString(36);
  props = Object.assign({}, options.attrs, options.props);
  el = document.createDocumentFragment();

  destroy = () => {
    app.unmount();
    app.$el.parentNode && app.$el.parentNode.removeChild(app.$el);
    app.$el = null;
    app.$ctx = null;
    app.$update = null;
    app.$destroy = null;
    app = null;
    el = null;
    key = null;
    ctx = null;
    context = null;
    props = null;
    update = null;
    destroy = null;
  };
  update = (updateProps) => {
    if (typeof updateProps === 'function') {
      updateProps = updateProps(props);
    }
    if (app && updateProps && typeof updateProps === 'object') {
      app._instance.props = mergeProps(app._instance.props, updateProps);
      app._instance.update();
    }
  };
  await new Promise((resolve) => {
    app = createApp({
      provide() {
        return { $destroy: destroy };
      },
      render() {
        return h(
          typeof App === 'function' ? defineAsyncComponent(App) : App,
          Object.assign({}, props, {
            key: key,
            ref: 'ins',
            onVnodeMounted() {
              if (typeof props.onVnodeMounted === 'function') {
                props.onVnodeMounted();
              }
              resolve();
            },
          }),
          options.slots
        );
      },
    });
    app._context = ctx.appContext || ctx._context || app._context;
    app.mount(el);
  });
  app._instance.parent = ctx.root || ctx._instance;
  app._instance.appContext = app._context;
  app.$ctx = app._instance.refs.ins;
  app.$el = el;
  app.$update = update;
  app.$destroy = destroy;
  return app;
};
